﻿#define _CRT_SECURE_NO_WARNINGS 1

// 1049. 最后一块石头的重量 II

//有一堆石头，用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
//
//每一回合，从中选出任意两块石头，然后将它们一起粉碎。假设石头的重量分别为 x 和 y，且 x <= y。那么粉碎的可能结果如下：
//
//如果 x == y，那么两块石头都会被完全粉碎；
//如果 x != y，那么重量为 x 的石头将会完全粉碎，而重量为 y 的石头新重量为 y - x。
//最后，最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下，就返回 0。
//
//示例 1：
//
//输入：stones = [2, 7, 4, 1, 8, 1]
//输出：1
//解释：
//组合 2 和 4，得到 2，所以数组转化为[2, 7, 1, 8, 1]，
//组合 7 和 8，得到 1，所以数组转化为[2, 1, 1, 1]，
//组合 2 和 1，得到 1，所以数组转化为[1, 1, 1]，
//组合 1 和 1，得到 0，所以数组转化为[1]，这就是最优值。
//示例 2：
//
//输入：stones = [31, 26, 33, 21, 40]
//输出：5



//01背包问题
//算法思路：
//先将问题 转化 成我们熟悉的题型。
//▪任意两块石头在一起粉碎，重量相同的部分会被丢掉，重量有差异的部分会被留下来。那就
//相当于在原始的数据的前面，加上「加号」或者「减号」，是最终的结果最小即可。
// 也就是说把原始的石头分成两部分，两部分的和越接近越好。
//▪ 又因为当所有元素的和固定时，分成的两部分越接近数组「总和的一半」，两者的差越小。
//因此问题就变成了：在数组中选择一些数，让这些数的和尽量接近 sum / 2 ，如果把数看成物
//品，每个数的值看成体积和价值，问题就变成了「01 背包问题」。
#include <vector>
using namespace std;
class Solution
{
public:
    int lastStoneWeightII(vector<int>& stones)
    {
        int n = stones.size(), sum = 0;
        for (int x : stones) sum += x;
        vector<vector<int>> dp(n + 1, vector<int>(sum / 2 + 1));
        //dp[i][j]:在前i个数中选，总和不超过j，此时所有元素的最大和
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= sum / 2; j++)
            {
                dp[i][j] = dp[i - 1][j];//不选
                if (j >= stones[i - 1]) dp[i][j] = max(dp[i][j], stones[i - 1] + dp[i - 1][j - stones[i - 1]]);//选
            }
        return sum - 2 * dp[n][sum / 2];
    }
};

//滚动数组优化

class Solution
{
public:
    int lastStoneWeightII(vector<int>& stones)
    {
        int n = stones.size(), sum = 0;
        for (int x : stones) sum += x;
        vector<int> dp(sum / 2 + 1);
        //dp[i][j]:在前i个数中选，总和不超过j，此时所有元素的最大和
        for (int i = 1; i <= n; i++)
            for (int j = sum / 2; j >= stones[i - 1]; j--)
            {
                dp[j] = max(dp[j], stones[i - 1] + dp[j - stones[i - 1]]);
            }
        return sum - 2 * dp[sum / 2];
    }
};